/*
 * =====================================================================================
 *
 *       Filename:  main.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2020年06月10日 13时28分43秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  YOUR NAME (), 
 *   Organization:  
 *
 * =====================================================================================
 */
#include "config.h"
#include "util_net.h"
#include "util_ubus.h"
#include "util_debug.h"
#include "util_event.h"
#include <pthread.h>

LIST_HEAD(mLink_head);

struct util_event *event = NULL;

static int SYSTEM(const char *fmt, ...)
{
    char buff[4096] = {0};
    va_list arg;
    va_start(arg, fmt);
    int ret = vsprintf(buff, fmt, arg);
    va_end(arg);
    if(ret > 0) {
        debug_print("%s\n", buff);
        system(buff);
    }
    return ret;
}

/* TODO:待改为原始套接字 */
static int ping_reachable(const char *from_eth, uint32_t to_ip, uint8_t timeout)
{
    char cmd[128] = {0};
	if(from_eth && from_eth[0]) {
		snprintf(cmd, sizeof(cmd) - 1, "ping -c 1 -W %d -I %s "IP_STR_FMT, timeout, from_eth, IP_STR_ARG(&to_ip));
	}
	else {
		snprintf(cmd, sizeof(cmd) - 1, "ping -c %d -W %d "IP_STR_FMT, timeout, IP_STR_ARG(&to_ip));
	}
    FILE *fp = popen(cmd, "r");
    if(NULL == fp) {
        perror("popen()");
        return -1;
    }
    char buff[1024] = {0};
    fread(buff, 1, sizeof(buff), fp);
    pclose(fp);
    if(strstr(buff, "100%")) {
        return 0;
    }
	else if(strstr(buff, " 0%")){
		return 1;
	}
	return -1;
}

uint8_t interface_link_status(struct mLink_interface *interface)
{
    /* return 1:在线, -1发生错误不知在不在线，认为在线 0:不在线 */
    return (ubus_get_net_up(interface->net) != 0)?INTERFACE_LINK_UP:INTERFACE_LINK_DOWN;
}

void interface_track_status_set(struct mLink_interface *interface, uint8_t track_status)
{
    if(track_status != interface->track_status) {
        debug_print("%s(): interface(%s) %s --> %s, event feed\n", 
                __func__, interface->net,
                interface->track_status==TRACK_STATUS_UP?"TRACK_STATUS_UP":"TRACK_STATUS_DOWN",
                track_status==TRACK_STATUS_UP?"TRACK_STATUS_UP":"TRACK_STATUS_DOWN");
        interface->track_status = track_status;
        util_event_feed(event);
        /* track_status更新到uci文件中 */
        char str_status[32] = {0};
        snprintf(str_status, sizeof(str_status) - 1, "%d", interface->track_status);
        util_uci_set("hsion_mLink", interface->uci_sec_name, "track_status", str_status);
    }
}

void thread_track(void *arg)
{
    pthread_detach(pthread_self()); 
    struct mLink_interface *interface = (struct mLink_interface *)arg;
    int up = 0;
    int down = 0;
    
    /* 初始的时候先触发一下 */
    util_event_feed(event); 
    while(1) {
        int ok = 0;

        /* 接口上下线检查 */
        if(INTERFACE_LINK_DOWN == interface_link_status(interface)) {
            down = interface->down;
            goto thread_track_point1;
        }

        char iface[32] = {0};
        if(ubus_get_net_interface(interface->net, iface, sizeof(iface) - 1) < 0) {
            debug_err("%s(): ubus_get_net_interface(%s) failed, probably is down\n", __func__, interface->net);
            continue;
        }

        struct list_head *pos;
        list_for_each(pos, &(interface->head_track_ip)) {
            struct track_ip *entry = list_entry(pos, struct track_ip, list);
            if(1 == ping_reachable(iface, entry->ip, interface->timeout)) {
                ok = 1;
                break;
            }
        }

        /* TODO:(优化)如果出现丢包那还要增加loss参数 */
        if(ok) {
            up += 1;
            down = 0;
        }
        else {
            down += 1;
            up = 0;
        }

thread_track_point1:
        if(up == interface->up) {
            up -= 1; /* 不置0,只减少1 */
            interface_track_status_set(interface, TRACK_STATUS_UP);
        }
        
        if(down == interface->down) {
            down -= 1;
            interface_track_status_set(interface, TRACK_STATUS_DOWN);
        }

        sleep(interface->interval);
    }
    pthread_exit(NULL);
}




void mLink_rule_reset_interface(struct mLink_interface *interface, int total_weight, const char *lan_iface, const char *lan_network)
{
    char iface[32] = {0};
    char gateway[24] = {0};
    uint8_t metric = interface->metric;
    if(ubus_get_net_gateway(interface->net, gateway, sizeof(gateway) - 1) < 0) {
        debug_err("%s(): ubus_get_net_gateway(%s) failed\n",
                __func__, interface->net);
        return;
    }
    debug_print("%s(): %s gateway:%s\n", __func__, interface->net, gateway);

    if(ubus_get_net_interface(interface->net, iface, sizeof(iface)   - 1) < 0) {
        debug_err("%s(): ubus_get_net_interface(%s) failed\n",
                __func__, interface->net);
        return;
    }
    debug_print("%s(): %s iface:%s\n", __func__, interface->net, iface);

    /* 接口SNAT */
    SYSTEM("iptables -w -t nat -A HSION_MLINK_N_POST -o %s -j MASQUERADE", iface);
    if(TRACK_STATUS_DOWN == interface->track_status) {
        return;
    }

    /* 接口网段 */
    uint32_t u32_ip = 0;
    uint32_t u32_netmask = 0;
    if(get_eth_u32_ip(iface, &u32_ip) < 0) {
        debug_err("%s(): get_eth_u32_ip(%s) failed\n", __func__, iface);
        return;
    }
    if(get_eth_u32_netmask(iface, &u32_netmask) < 0) {
        debug_err("%s(): get_eth_u32_netmask(%s) failed\n", __func__, iface);
        return;
    }
    u32_ip = (u32_ip & u32_netmask);

    /* 给每个连接打上mark */
    if(interface->weight == total_weight) {
        SYSTEM("iptables -w -t mangle -A HSION_MLINK_M_PRE -m conntrack --ctstate NEW"
               " -j CONNMARK --set-mark %u", metric);
    }
    else {
        SYSTEM("iptables -w -t mangle -A HSION_MLINK_M_PRE -m conntrack --ctstate NEW"
               " -m statistic --mode random --probability %f -j CONNMARK --set-mark %u",
               ((float)interface->weight)/((float)total_weight), metric);
    }
    /* 把连接的mark打到每个packet上 */
    SYSTEM("iptables -w -t mangle -A HSION_MLINK_M_PRE -m connmark --mark %u -j MARK"
            " --set-mark %u", metric, metric);
    /* 只允许打上mark的packet通过 */
    SYSTEM("iptables -w -t filter -A HSION_MLINK_F_FWD -m mark --mark %u -j ACCEPT", metric);

    /* 添加策略路由规则 */
    SYSTEM("ip rule del table %u", metric);
    SYSTEM("ip rule add fwmark %u table %u prio %u", 
            metric, metric, metric);

    /* 添加路由表规则 */
    SYSTEM("ip route flush table %u", metric);
    SYSTEM("ip route add default via %s dev %s table %u", gateway, iface, metric);
    /* lan口的直连路由 */
    SYSTEM("ip route add %s dev %s table %u", lan_network, lan_iface, metric);
    /* 接口的直连路由:不加的话会导致带宽严重降低1000M变100M */
    SYSTEM("ip route add "IP_STR_FMT"/%d dev %s table %u", 
            IP_STR_ARG(&u32_ip), netmask_u32_to_bits(u32_netmask), iface, metric);
}

uint32_t mLink_interface_total_weight(struct list_head *head)
{
    uint32_t weight = 0;
    struct list_head *pos;
    list_for_each(pos, head) {
        struct mLink_interface *interface = 
                        list_entry(pos, struct mLink_interface, list);

        if(TRACK_STATUS_DOWN == interface->track_status) {
            /* down掉的接口不在统计范围内 */
            continue;
        }
        weight += interface->weight;
    }
    return weight;
}

void mLink_rule_reset(struct list_head *head)
{
    SYSTEM("iptables -P FORWARD DROP");

    SYSTEM("iptables -w -t filter -D FORWARD -j HSION_MLINK_F_FWD");
    SYSTEM("iptables -w -t filter -F HSION_MLINK_F_FWD");
    SYSTEM("iptables -w -t filter -X HSION_MLINK_F_FWD");
    SYSTEM("iptables -w -t filter -N HSION_MLINK_F_FWD");
    SYSTEM("iptables -w -t filter -A FORWARD -j HSION_MLINK_F_FWD");

    SYSTEM("iptables -w -t nat -D POSTROUTING -j HSION_MLINK_N_POST");
    SYSTEM("iptables -w -t nat -F HSION_MLINK_N_POST");
    SYSTEM("iptables -w -t nat -X HSION_MLINK_N_POST");
    SYSTEM("iptables -w -t nat -N HSION_MLINK_N_POST");
    SYSTEM("iptables -w -t nat -A POSTROUTING -j HSION_MLINK_N_POST");

    SYSTEM("iptables -w -t mangle -D PREROUTING -j HSION_MLINK_M_PRE");
    SYSTEM("iptables -w -t mangle -F HSION_MLINK_M_PRE");
    SYSTEM("iptables -w -t mangle -X HSION_MLINK_M_PRE");
    SYSTEM("iptables -w -t mangle -N HSION_MLINK_M_PRE");
    SYSTEM("iptables -w -t mangle -A PREROUTING -j HSION_MLINK_M_PRE");

    /* TODO:lan接口 */
    char lan_iface[32] = {0};
    char lan_network[32] = {0};

    if(ubus_get_net_interface("lan", lan_iface, sizeof(lan_iface) - 1) < 0) {
        debug_err("%s(): ubus_get_net_interface(lan) failed\n", __func__);
        return;
    }

    uint32_t u32_lan_ip;
    uint32_t u32_lan_netmask;
    if(get_eth_u32_ip(lan_iface, &u32_lan_ip) < 0) {
        debug_err("%s(): get_eth_u32_ip(%s) failed\n", __func__, lan_iface);
        return;
    }
    if(get_eth_u32_netmask(lan_iface, &u32_lan_netmask) < 0) {
        debug_err("%s(): get_eth_u32_netmask(%s) failed\n", __func__, lan_iface);
        return;
    }

    u32_lan_ip = (u32_lan_ip & u32_lan_netmask);
    snprintf(lan_network, sizeof(lan_network) - 1, IP_STR_FMT"/%d",
            IP_STR_ARG(&u32_lan_ip), netmask_u32_to_bits(u32_lan_netmask));

    uint32_t total_weight = mLink_interface_total_weight(head);

    /* 为每个接口添加规则 */
    struct list_head *pos;
    list_for_each(pos, head) {
        struct mLink_interface *interface = 
                        list_entry(pos, struct mLink_interface, list);

        mLink_rule_reset_interface(interface, total_weight, lan_iface, lan_network);
    }
}

int main(int argc, char **argv)
{
    /* 配置文件解析 */
    int enabled = 0;
    if(hsion_mLink_config_init(&mLink_head, &enabled) < 0) {
        debug_err("%s: hsion_mLink_config_init() failed\n", __func__);
        return -1;
    }

    if(!enabled) {
        debug_print("%s(): hsion_mLink is not enabled\n", __func__);
        return 0;
    }
    

    event = util_event_create();
    if(NULL == event) {
        debug_err("%s(): util_event_create() failed\n", __func__);
        return -1;
    }
    
    struct list_head *pos;
    list_for_each(pos, &mLink_head) {
        struct mLink_interface *entry = list_entry(pos, struct mLink_interface, list);
        /* 初始化状态更新至文件 */
        char str_status[32] = {0};
        snprintf(str_status, sizeof(str_status) - 1, "%d", entry->track_status);
        util_uci_set("hsion_mLink", entry->uci_sec_name, "track_status", str_status);

        pthread_t th;
        if(0 != pthread_create(&th, NULL, (void *)&thread_track, (void *)entry)) {
            debug_err("%s(): pthread_create(thread_track) for %s\n", __func__, entry->net);
            break;
        }
    }

    util_event_feed(event); 
    while(1) {
        util_event_eat(event); 
        /* 等待事件发重新规划规则 */
        mLink_rule_reset(&mLink_head);
    }

    return 0;
}


